<!DOCTYPE html>
<html>

<head>
  <title>Quarkus - Application Data Caching</title>
  <script id="adobe_dtm" src="https://www.redhat.com/dtm.js" type="text/javascript"></script>
  <script src="/assets/javascript/highlight.pack.js" type="text/javascript"></script>
  <META HTTP-EQUIV='Content-Security-Policy' CONTENT="default-src 'none'; script-src 'self' 'unsafe-eval' 'sha256-ANpuoVzuSex6VhqpYgsG25OHWVA1I+F6aGU04LoI+5s=' 'sha256-ipy9P/3rZZW06mTLAR0EnXvxSNcnfSDPLDuh3kzbB1w=' js.bizographics.com https://www.redhat.com assets.adobedtm.com jsonip.com https://ajax.googleapis.com https://www.googletagmanager.com https://www.google-analytics.com https://use.fontawesome.com; style-src 'self' https://fonts.googleapis.com https://use.fontawesome.com; img-src 'self' *; media-src 'self' ; frame-src https://www.googletagmanager.com https://www.youtube.com; frame-ancestors 'none'; base-uri 'none'; object-src 'none'; form-action 'none'; font-src 'self' https://use.fontawesome.com https://fonts.gstatic.com;">
  <META HTTP-EQUIV='X-Frame-Options' CONTENT="DENY">
  <META HTTP-EQUIV='X-XSS-Protection' CONTENT="1; mode=block">
  <META HTTP-EQUIV='X-Content-Type-Options' CONTENT="nosniff">
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Quarkus: Supersonic Subatomic Java">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@QuarkusIO"> 
  <meta name="twitter:creator" content="@QuarkusIO">
  <meta property="og:url" content="https://quarkus.io/guides/cache" />
  <meta property="og:title" content="Quarkus - Application Data Caching" />
  <meta property="og:description" content="Quarkus: Supersonic Subatomic Java" />
  <meta property="og:image" content="/assets/images/quarkus_card.png" />
  <link rel="canonical" href="https://quarkus.io/guides/cache">
  <link rel="shortcut icon" type="image/png" href="/favicon.ico" >
  <link rel="stylesheet" href="https://quarkus.io/guides/stylesheet/config.css" />
  <link rel="stylesheet" href="/assets/css/main.css" />
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
  <link rel="alternate" type="application/rss+xml"  href="https://quarkus.io/feed.xml" title="Quarkus">
  <script src="https://quarkus.io/assets/javascript/goan.js" type="text/javascript"></script>
  <script src="https://quarkus.io/assets/javascript/hl.js" type="text/javascript"></script>
</head>

<body class="guides">
  <!-- Google Tag Manager (noscript) -->
  <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-NJWS5L"
  height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
  <!-- End Google Tag Manager (noscript) -->

  <div class="nav-wrapper">
  <div class="grid-wrapper">
    <div class="width-12-12">
      <input type="checkbox" id="checkbox" />
      <nav id="main-nav" class="main-nav">
  <div class="container">
    <div class="logo-wrapper">
      
        <a href="/"><img src="/assets/images/quarkus_logo_horizontal_rgb_600px_reverse.png" class="project-logo" title="Quarkus"></a>
      
    </div>
    <label class="nav-toggle" for="checkbox">
      <i class="fa fa-bars"></i>
    </label>
    <div id="menu" class="menu">
      <span>
        <a href="/get-started/" class="">Get Started</a>
      </span>
      <span>
        <a href="/guides/" class="active">Guides</a>
      </span>
      <span>
        <a href="/community/" class="">Community</a>
      </span>
      <span>
        <a href="/support/" class="">Support</a>
      </span>
      <span>
        <a href="/blog/" class="">Blog</a>
      </span>
      <span>
        <a href="https://code.quarkus.io" class="button-cta secondary white">Start Coding</a>
      </span>
    </div>
  </div>
      </nav>
    </div>
  </div>
</div>

  <div class="content">
    <div class="guide">
  <div class="width-12-12">
    <h1 class="text-caps">Quarkus - Application Data Caching</h1>
    <div class="hide-mobile toc"><ul class="sectlevel1">
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#scenario">Scenario</a></li>
<li><a href="#solution">Solution</a></li>
<li><a href="#creating-the-maven-project">Creating the Maven project</a></li>
<li><a href="#creating-the-rest-api">Creating the REST API</a></li>
<li><a href="#enabling-the-cache">Enabling the cache</a></li>
<li><a href="#caching-annotations">Caching annotations</a>
<ul class="sectlevel2">
<li><a href="#cacheresult">@CacheResult</a></li>
<li><a href="#cacheinvalidate">@CacheInvalidate</a></li>
<li><a href="#cacheinvalidateall">@CacheInvalidateAll</a></li>
<li><a href="#cachekey">@CacheKey</a></li>
<li><a href="#composite-cache-key-building-logic">Composite cache key building logic</a></li>
</ul>
</li>
<li><a href="#configuring-the-underlying-caching-provider">Configuring the underlying caching provider</a>
<ul class="sectlevel2">
<li><a href="#caffeine-configuration-properties">Caffeine configuration properties</a></li>
</ul>
</li>
<li><a href="#context-propagation">Context propagation</a></li>
<li><a href="#annotated-beans-examples">Annotated beans examples</a>
<ul class="sectlevel2">
<li><a href="#implicit-simple-cache-key">Implicit simple cache key</a></li>
<li><a href="#explicit-composite-cache-key">Explicit composite cache key</a></li>
<li><a href="#default-cache-key">Default cache key</a></li>
<li><a href="#multiple-annotations-on-a-single-method">Multiple annotations on a single method</a></li>
</ul>
</li>
<li><a href="#negative-cache">Negative caching and nulls</a>
<ul class="sectlevel2">
<li><a href="#let-exceptions-bubble-up">Let exceptions bubble up</a></li>
</ul>
</li>
</ul></div>
    <div>
      <div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>In this guide, you will learn how to enable application data caching in any CDI managed bean of your Quarkus application.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>This technology is considered preview.</p>
</div>
<div class="paragraph">
<p>In <em>preview</em>, backward compatibility and presence in the ecosystem is not guaranteed.
Specific improvements might require to change configuration or APIs and plans to become <em>stable</em> are under way.
Feedback is welcome on our <a href="https://groups.google.com/d/forum/quarkus-dev">mailing list</a> or as issues in our <a href="https://github.com/quarkusio/quarkus/issues">GitHub issue tracker</a>.</p>
</div>
<div class="paragraph">
<p>For a full list of possible extension statuses, check our <a href="https://quarkus.io/faq/#extension-status">FAQ entry</a>.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="prerequisites"><a class="anchor" href="#prerequisites"></a>Prerequisites</h2>
<div class="sectionbody">
<div class="paragraph">
<p>To complete this guide, you need:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>less than 15 minutes</p>
</li>
<li>
<p>an IDE</p>
</li>
<li>
<p>JDK 1.8+ installed with <code>JAVA_HOME</code> configured appropriately</p>
</li>
<li>
<p>Apache Maven 3.6.2+</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="scenario"><a class="anchor" href="#scenario"></a>Scenario</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let&#8217;s imagine you want to expose in your Quarkus application a REST API that allows users to retrieve the weather forecast for the next three days.
The problem is that you have to rely on an external meteorological service which only accepts requests for one day at a time and takes forever to answer.
Since the weather forecast is updated once every twelve hours, caching the service responses would definitely improve your API performances.</p>
</div>
<div class="paragraph">
<p>We&#8217;ll do that using a single Quarkus annotation.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="solution"><a class="anchor" href="#solution"></a>Solution</h2>
<div class="sectionbody">
<div class="paragraph">
<p>We recommend that you follow the instructions in the next sections and create the application step by step.
However, you can go right to the completed example.</p>
</div>
<div class="paragraph">
<p>Clone the Git repository: <code>git clone <a href="https://github.com/quarkusio/quarkus-quickstarts.git" class="bare">https://github.com/quarkusio/quarkus-quickstarts.git</a></code>, or download an <a href="https://github.com/quarkusio/quarkus-quickstarts/archive/master.zip">archive</a>.</p>
</div>
<div class="paragraph">
<p>The solution is located in the <code>cache-quickstart</code> directory.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="creating-the-maven-project"><a class="anchor" href="#creating-the-maven-project"></a>Creating the Maven project</h2>
<div class="sectionbody">
<div class="paragraph">
<p>First, we need to create a new Quarkus project using Maven with the following command:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="shell" class="language-shell hljs">mvn io.quarkus:quarkus-maven-plugin:1.7.0.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=cache-quickstart \
    -DclassName="org.acme.cache.WeatherForecastResource" \
    -Dpath="/weather" \
    -Dextensions="cache,resteasy-jsonb"</code></pre>
</div>
</div>
<div class="paragraph">
<p>This command generates the Maven project with a REST endpoint and imports the <code>cache</code> and <code>resteasy-jsonb</code> extensions.</p>
</div>
<div class="paragraph">
<p>If you already have your Quarkus project configured, you can add the <code>cache</code> extension
to your project by running the following command in your project base directory:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="bash" class="language-bash hljs">./mvnw quarkus:add-extension -Dextensions="cache"</code></pre>
</div>
</div>
<div class="paragraph">
<p>This will add the following to your <code>pom.xml</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="xml" class="language-xml hljs">&lt;dependency&gt;
    &lt;groupId&gt;io.quarkus&lt;/groupId&gt;
    &lt;artifactId&gt;quarkus-cache&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="creating-the-rest-api"><a class="anchor" href="#creating-the-rest-api"></a>Creating the REST API</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let&#8217;s start by creating a service that will simulate an extremely slow call to the external meteorological service.
Create <code>src/main/java/org/acme/cache/WeatherForecastService.java</code> with the following content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import java.time.LocalDate;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class WeatherForecastService {

    public String getDailyForecast(LocalDate date, String city) {
        try {
            Thread.sleep(2000L); <i class="conum" data-value="1"></i><b>(1)</b>
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
    }

    private String getDailyResult(int dayOfMonthModuloFour) {
        switch (dayOfMonthModuloFour) {
            case 0:
                return "sunny";
            case 1:
                return "cloudy";
            case 2:
                return "chilly";
            case 3:
                return "rainy";
            default:
                throw new IllegalArgumentException();
        }
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>This is where the slowness comes from.</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>We also need a class that will contain the response sent to the users when they ask for the next three days weather forecast.
Create <code>src/main/java/org/acme/cache/WeatherForecast.java</code> this way:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import java.util.List;

public class WeatherForecast {

    private List&lt;String&gt; dailyForecasts;

    private long executionTimeInMs;

    public WeatherForecast(List&lt;String&gt; dailyForecasts, long executionTimeInMs) {
        this.dailyForecasts = dailyForecasts;
        this.executionTimeInMs = executionTimeInMs;
    }

    public List&lt;String&gt; getDailyForecasts() {
        return dailyForecasts;
    }

    public long getExecutionTimeInMs() {
        return executionTimeInMs;
    }
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now, we just need to update the generated <code>WeatherForecastResource</code> class to use the service and response:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.jaxrs.QueryParam;

@Path("/weather")
public class WeatherForecastResource {

    @Inject
    WeatherForecastService service;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public WeatherForecast getForecast(@QueryParam String city, @QueryParam long daysInFuture) { <i class="conum" data-value="1"></i><b>(1)</b>
        long executionStart = System.currentTimeMillis();
        List&lt;String&gt; dailyForecasts = Arrays.asList(
                service.getDailyForecast(LocalDate.now().plusDays(daysInFuture), city),
                service.getDailyForecast(LocalDate.now().plusDays(daysInFuture + 1L), city),
                service.getDailyForecast(LocalDate.now().plusDays(daysInFuture + 2L), city)
        );
        long executionEnd = System.currentTimeMillis();
        return new WeatherForecast(dailyForecasts, executionEnd - executionStart);
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>If the <code>daysInFuture</code> query parameter is omitted, the three days weather forecast will start from the current day.
Otherwise, it will start from the current day plus the <code>daysInFuture</code> value.</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>We&#8217;re all done! Let&#8217;s check if everything&#8217;s working.</p>
</div>
<div class="paragraph">
<p>First, run the application using <code>./mvnw compile quarkus:dev</code> from the project directory.</p>
</div>
<div class="paragraph">
<p>Then, call <code><a href="http://localhost:8080/weather?city=Raleigh" class="bare">http://localhost:8080/weather?city=Raleigh</a></code> from a browser.
After six long seconds, the application will answer something like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-none hljs">{"dailyForecasts":["MONDAY will be cloudy in Raleigh","TUESDAY will be chilly in Raleigh","WEDNESDAY will be rainy in Raleigh"],"executionTimeInMs":6001}</code></pre>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<div class="paragraph">
<p>The response content may vary depending on the day you run the code.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>You can try calling the same URL again and again, it will always take six seconds to answer.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="enabling-the-cache"><a class="anchor" href="#enabling-the-cache"></a>Enabling the cache</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now that your Quarkus application is up and running, let&#8217;s tremendously improve its response time by caching the external meteorological service responses.
Update the <code>WeatherForecastService</code> class like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import java.time.LocalDate;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.cache.CacheResult;

@ApplicationScoped
public class WeatherForecastService {

    @CacheResult(cacheName = "weather-cache") <i class="conum" data-value="1"></i><b>(1)</b>
    public String getDailyForecast(LocalDate date, String city) {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
    }

    private String getDailyResult(int dayOfMonthModuloFour) {
        switch (dayOfMonthModuloFour) {
            case 0:
                return "sunny";
            case 1:
                return "cloudy";
            case 2:
                return "chilly";
            case 3:
                return "rainy";
            default:
                throw new IllegalArgumentException();
        }
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>We only added this annotation (and the associated import of course).</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Let&#8217;s try to call <code><a href="http://localhost:8080/weather?city=Raleigh" class="bare">http://localhost:8080/weather?city=Raleigh</a></code> again.
You&#8217;re still waiting a long time before receiving an answer.
This is normal since the server just restarted and the cache was empty.</p>
</div>
<div class="paragraph">
<p>Wait a second! The server restarted by itself after the <code>WeatherForecastService</code> update?
Yes, this is one of Quarkus amazing features for developers called <code>live coding</code>.</p>
</div>
<div class="paragraph">
<p>Now that the cache was loaded during the previous call, try calling the same URL.
This time, you should get a super fast answer with an <code>executionTimeInMs</code> value close to 0.</p>
</div>
<div class="paragraph">
<p>Let&#8217;s see what happens if we start from one day in the future using the <code><a href="http://localhost:8080/weather?city=Raleigh&amp;daysInFuture=1" class="bare">http://localhost:8080/weather?city=Raleigh&amp;daysInFuture=1</a></code> URL.
You should get an answer two seconds later since two of the requested days were already loaded in the cache.</p>
</div>
<div class="paragraph">
<p>You can also try calling the same URL with a different city and see the cache in action again.
The first call will take six seconds and the following ones will be answered immediately.</p>
</div>
<div class="paragraph">
<p>Congratulations! You just added application data caching to your Quarkus application with a single line of code!</p>
</div>
<div class="paragraph">
<p>Do you want to learn more about the Quarkus application data caching abilities?
The following sections will show you everything there is to know about it.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="caching-annotations"><a class="anchor" href="#caching-annotations"></a>Caching annotations</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Quarkus offers a set of annotations that can be used in a CDI managed bean to enable caching abilities.</p>
</div>
<div class="sect2">
<h3 id="cacheresult"><a class="anchor" href="#cacheresult"></a>@CacheResult</h3>
<div class="paragraph">
<p>Loads a method result from the cache without executing the method body whenever possible.</p>
</div>
<div class="paragraph">
<p>When a method annotated with <code>@CacheResult</code> is invoked, Quarkus will compute a cache key and use it to check in the cache whether the method has been already invoked.
If the method has one or more arguments, the key computation is done from all the method arguments if none of them is annotated with <code>@CacheKey</code>, or all the arguments annotated with <code>@CacheKey</code> otherwise.
Each non-primitive method argument that is part of the key must implement <code>equals()</code> and <code>hashCode()</code> correctly for the cache to work as expected.
This annotation can also be used on a method with no arguments, a default key derived from the cache name is generated in that case.
If a value is found in the cache, it is returned and the annotated method is never actually executed.
If no value is found, the annotated method is invoked and the returned value is stored in the cache using the computed or generated key.</p>
</div>
<div class="paragraph">
<p>A method annotated with <code>CacheResult</code> is protected by a lock on cache miss mechanism.
If several concurrent invocations try to retrieve a cache value from the same missing key, the method will only be invoked once.
The first concurrent invocation will trigger the method invocation while the subsequent concurrent invocations will wait for the end of the method invocation to get the cached result.
The <code>lockTimeout</code> parameter can be used to interrupt the lock after a given delay.
The lock timeout is disabled by default, meaning the lock is never interrupted.
See the parameter Javadoc for more details.</p>
</div>
<div class="paragraph">
<p>This annotation cannot be used on a method returning <code>void</code>.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>Quarkus is able to also cache <code>null</code> values unlike the underlying Caffeine provider.
See <a href="#negative-cache">more on this topic below</a>.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="cacheinvalidate"><a class="anchor" href="#cacheinvalidate"></a>@CacheInvalidate</h3>
<div class="paragraph">
<p>Removes an entry from the cache.</p>
</div>
<div class="paragraph">
<p>When a method annotated with <code>@CacheInvalidate</code> is invoked, Quarkus will compute a cache key and use it to try to remove an existing entry from the cache.
If the method has one or more arguments, the key computation is done from all the method arguments if none of them is annotated with <code>@CacheKey</code>, or all the arguments annotated with <code>@CacheKey</code> otherwise.
This annotation can also be used on a method with no arguments, a default key derived from the cache name is generated in that case.
If the key does not identify any cache entry, nothing will happen.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<div class="paragraph">
<p>If the <code>@CacheResult</code> or <code>@CacheInvalidate</code> annotations are used on a method with no parameters, a unique default cache key derived from the cache name will be generated and used.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="cacheinvalidateall"><a class="anchor" href="#cacheinvalidateall"></a>@CacheInvalidateAll</h3>
<div class="paragraph">
<p>When a method annotated with <code>@CacheInvalidateAll</code> is invoked, Quarkus will remove all entries from the cache.</p>
</div>
</div>
<div class="sect2">
<h3 id="cachekey"><a class="anchor" href="#cachekey"></a>@CacheKey</h3>
<div class="paragraph">
<p>When a method argument is annotated with <code>@CacheKey</code>, it is identified as a part of the cache key during an invocation of a
method annotated with <code>@CacheResult</code> or <code>@CacheInvalidate</code>.</p>
</div>
<div class="paragraph">
<p>This annotation is optional and should only be used when some of the method arguments are NOT part of the cache key.</p>
</div>
</div>
<div class="sect2">
<h3 id="composite-cache-key-building-logic"><a class="anchor" href="#composite-cache-key-building-logic"></a>Composite cache key building logic</h3>
<div class="paragraph">
<p>When a cache key is built from several method arguments, whether they are explicitly identified with <code>@CacheKey</code> or not, the building logic depends on the order of these arguments in the method signature. On the other hand, the arguments names are not used at all and do not have any effect on the cache key.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.cache.CacheInvalidate;
import io.quarkus.cache.CacheResult;

@ApplicationScoped
public class CachedService {

    @CacheResult(cacheName = "foo")
    public Object load(String keyElement1, Integer keyElement2) {
        // Call expensive service here.
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate1(String keyElement2, Integer keyElement1) { <i class="conum" data-value="1"></i><b>(1)</b>
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate2(Integer keyElement2, String keyElement1) { <i class="conum" data-value="2"></i><b>(2)</b>
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate3(Object notPartOfTheKey, @CacheKey String keyElement1, @CacheKey Integer keyElement2) { <i class="conum" data-value="3"></i><b>(3)</b>
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate4(Object notPartOfTheKey, @CacheKey Integer keyElement2, @CacheKey String keyElement1) { <i class="conum" data-value="4"></i><b>(4)</b>
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>Calling this method WILL invalidate values cached by the <code>load</code> method even if the key elements names have been swapped.</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>Calling this method WILL NOT invalidate values cached by the <code>load</code> method because the key elements order is different.</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>Calling this method WILL invalidate values cached by the <code>load</code> method because the key elements order is the same.</td>
</tr>
<tr>
<td><i class="conum" data-value="4"></i><b>4</b></td>
<td>Calling this method WILL NOT invalidate values cached by the <code>load</code> method because the key elements order is different.</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="configuring-the-underlying-caching-provider"><a class="anchor" href="#configuring-the-underlying-caching-provider"></a>Configuring the underlying caching provider</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This extension uses <a href="https://github.com/ben-manes/caffeine">Caffeine</a> as its underlying caching provider.
Caffeine is a high performance, near optimal caching library.</p>
</div>
<div class="sect2">
<h3 id="caffeine-configuration-properties"><a class="anchor" href="#caffeine-configuration-properties"></a>Caffeine configuration properties</h3>
<div class="paragraph">
<p>Each of the Caffeine caches backing up the Quarkus application data caching extension can be configured using the following
properties in the <code>application.properties</code> file. By default caches do not perform any type of eviction if not configured.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<i class="fa icon-tip" title="Tip"></i>
</td>
<td class="content">
<div class="paragraph">
<p>You need to replace <code>cache-name</code> in all of the following properties with the real name of the cache you want to configure.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph configuration-legend">
<p><span class="icon"><i class="fa fa-lock" title="Fixed at build time"></i></span> Configuration property fixed at build time - All other configuration properties are overridable at runtime</p>
</div>
<table class="tableblock frame-all grid-all stretch configuration-reference">
<colgroup>
<col style="width: 80%;">
<col style="width: 10%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr>
<th class="tableblock halign-left valign-top"><p class="tableblock"><a id="quarkus-cache-config-group-cache-config-caffeine-config_configuration"></a><a href="#quarkus-cache-config-group-cache-config-caffeine-config_configuration">Configuration property</a></p></th>
<th class="tableblock halign-left valign-middle"><p class="tableblock">Type</p></th>
<th class="tableblock halign-left valign-middle"><p class="tableblock">Default</p></th>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph">
<p><span class="icon"><i class="fa fa-lock" title="Fixed at build time"></i></span> <a id="quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.initial-capacity"></a><code><a href="#quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.initial-capacity">quarkus.cache.caffeine."cache-name".initial-capacity</a></code></p>
</div>
<div class="openblock description">
<div class="content">
<div class="paragraph">
<p>Minimum total size for the internal data structures. Providing a large enough estimate at construction time avoids the need for expensive resizing operations later, but setting this value unnecessarily high wastes memory.</p>
</div>
</div>
</div></div></td>
<td class="tableblock halign-left valign-middle"><p class="tableblock">int</p></td>
<td class="tableblock halign-left valign-middle"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph">
<p><span class="icon"><i class="fa fa-lock" title="Fixed at build time"></i></span> <a id="quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.maximum-size"></a><code><a href="#quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.maximum-size">quarkus.cache.caffeine."cache-name".maximum-size</a></code></p>
</div>
<div class="openblock description">
<div class="content">
<div class="paragraph">
<p>Maximum number of entries the cache may contain. Note that the cache <strong>may evict an entry before this limit is exceeded or temporarily exceed the threshold while evicting</strong>. As the cache size grows close to the maximum, the cache evicts entries that are less likely to be used again. For example, the cache may evict an entry because it hasn&#8217;t been used recently or very often.</p>
</div>
</div>
</div></div></td>
<td class="tableblock halign-left valign-middle"><p class="tableblock">long</p></td>
<td class="tableblock halign-left valign-middle"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph">
<p><span class="icon"><i class="fa fa-lock" title="Fixed at build time"></i></span> <a id="quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.expire-after-write"></a><code><a href="#quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.expire-after-write">quarkus.cache.caffeine."cache-name".expire-after-write</a></code></p>
</div>
<div class="openblock description">
<div class="content">
<div class="paragraph">
<p>Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry&#8217;s creation, or the most recent replacement of its value.</p>
</div>
</div>
</div></div></td>
<td class="tableblock halign-left valign-middle"><p class="tableblock"><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html">Duration</a>
  <a href="#duration-note-anchor" title="More information about the Duration format"><span class="icon"><i class="fa fa-question-circle"></i></span></a></p></td>
<td class="tableblock halign-left valign-middle"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph">
<p><span class="icon"><i class="fa fa-lock" title="Fixed at build time"></i></span> <a id="quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.expire-after-access"></a><code><a href="#quarkus-cache-config-group-cache-config-caffeine-config_quarkus.cache.caffeine.-cache-name-.expire-after-access">quarkus.cache.caffeine."cache-name".expire-after-access</a></code></p>
</div>
<div class="openblock description">
<div class="content">
<div class="paragraph">
<p>Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry&#8217;s creation, the most recent replacement of its value, or its last read.</p>
</div>
</div>
</div></div></td>
<td class="tableblock halign-left valign-middle"><p class="tableblock"><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html">Duration</a>
  <a href="#duration-note-anchor" title="More information about the Duration format"><span class="icon"><i class="fa fa-question-circle"></i></span></a></p></td>
<td class="tableblock halign-left valign-middle"></td>
</tr>
</tbody>
</table>
<div id="duration-note-anchor" class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="title">About the Duration format</div>
<div class="paragraph">
<p>The format for durations uses the standard <code>java.time.Duration</code> format.
You can learn more about it in the <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-">Duration#parse() javadoc</a>.</p>
</div>
<div class="paragraph">
<p>You can also provide duration values starting with a number.
In this case, if the value consists only of a number, the converter treats the value as seconds.
Otherwise, <code>PT</code> is implicitly prepended to the value to obtain a standard <code>java.time.Duration</code> format.</p>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Here&#8217;s what your cache configuration could look like:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="properties" class="language-properties hljs">quarkus.cache.caffeine."foo".initial-capacity=10 <i class="conum" data-value="1"></i><b>(1)</b>
quarkus.cache.caffeine."foo".maximum-size=20
quarkus.cache.caffeine."foo".expire-after-write=60S
quarkus.cache.caffeine."bar".maximum-size=1000 <i class="conum" data-value="2"></i><b>(2)</b></code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>The <code>foo</code> cache is being configured.</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>The <code>bar</code> cache is being configured.</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="context-propagation"><a class="anchor" href="#context-propagation"></a>Context propagation</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This extension relies on non-blocking calls internally for cache values computations.
By default, there&#8217;s no context propagation between the calling thread (from your application) and a thread that performs such a computation.</p>
</div>
<div class="paragraph">
<p>The context propagation can be enabled for this extension by simply adding the <code>quarkus-smallrye-context-propagation</code> extension to your project.</p>
</div>
<div class="paragraph">
<p>If you see a <code>javax.enterprise.context.ContextNotActiveException</code> in your application log during a cache computation, then you probably need to enable the context propagation.</p>
</div>
<div class="paragraph">
<p>You can find more information about context propagation in Quarkus in the <a href="context-propagation">dedicated guide</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="annotated-beans-examples"><a class="anchor" href="#annotated-beans-examples"></a>Annotated beans examples</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="implicit-simple-cache-key"><a class="anchor" href="#implicit-simple-cache-key"></a>Implicit simple cache key</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.cache.CacheInvalidate;
import io.quarkus.cache.CacheInvalidateAll;
import io.quarkus.cache.CacheResult;

@ApplicationScoped
public class CachedService {

    @CacheResult(cacheName = "foo")
    public Object load(Object key) { <i class="conum" data-value="1"></i><b>(1)</b>
        // Call expensive service here.
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate(Object key) { <i class="conum" data-value="1"></i><b>(1)</b>
    }

    @CacheInvalidateAll(cacheName = "foo")
    public void invalidateAll() {
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>The cache key is implicit since there&#8217;s no <code>@CacheKey</code> annotation.</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="explicit-composite-cache-key"><a class="anchor" href="#explicit-composite-cache-key"></a>Explicit composite cache key</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import javax.enterprise.context.Dependent;

import io.quarkus.cache.CacheInvalidate;
import io.quarkus.cache.CacheInvalidateAll;
import io.quarkus.cache.CacheKey;
import io.quarkus.cache.CacheResult;

@Dependent
public class CachedService {

    @CacheResult(cacheName = "foo")
    public String load(@CacheKey Object keyElement1, @CacheKey Object keyElement2, Object notPartOfTheKey) { <i class="conum" data-value="1"></i><b>(1)</b>
        // Call expensive service here.
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate(@CacheKey Object keyElement1, @CacheKey Object keyElement2, Object notPartOfTheKey) { <i class="conum" data-value="1"></i><b>(1)</b>
    }

    @CacheInvalidateAll(cacheName = "foo")
    public void invalidateAll() {
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>The cache key is explicitly composed of two elements. The method signature also contains a third argument which is not part of the key.</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="default-cache-key"><a class="anchor" href="#default-cache-key"></a>Default cache key</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import javax.enterprise.context.Dependent;

import io.quarkus.cache.CacheInvalidate;
import io.quarkus.cache.CacheInvalidateAll;
import io.quarkus.cache.CacheResult;

@Dependent
public class CachedService {

    @CacheResult(cacheName = "foo")
    public String load() { <i class="conum" data-value="1"></i><b>(1)</b>
        // Call expensive service here.
    }

    @CacheInvalidate(cacheName = "foo")
    public void invalidate() { <i class="conum" data-value="1"></i><b>(1)</b>
    }

    @CacheInvalidateAll(cacheName = "foo")
    public void invalidateAll() {
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>A unique default cache key derived from the cache name is generated and used.</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="multiple-annotations-on-a-single-method"><a class="anchor" href="#multiple-annotations-on-a-single-method"></a>Multiple annotations on a single method</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.cache;

import javax.inject.Singleton;

import io.quarkus.cache.CacheInvalidate;
import io.quarkus.cache.CacheInvalidateAll;
import io.quarkus.cache.CacheResult;

@Singleton
public class CachedService {

    @CacheInvalidate(cacheName = "foo")
    @CacheResult(cacheName = "foo")
    public String forceCacheEntryRefresh(Object key) { <i class="conum" data-value="1"></i><b>(1)</b>
        // Call expensive service here.
    }

    @CacheInvalidateAll(cacheName = "foo")
    @CacheInvalidateAll(cacheName = "bar")
    public void multipleInvalidateAll(Object key) { <i class="conum" data-value="2"></i><b>(2)</b>
    }
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>This method can be used to force a refresh of the cache entry corresponding to the given key.</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>This method will invalidate all entries from the <code>foo</code> and <code>bar</code> caches with a single call.</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="negative-cache"><a class="anchor" href="#negative-cache"></a>Negative caching and nulls</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Sometimes one wants to cache the results of an (expensive) remote call.
If the remote call fails, one may not want to cache the result or exception,
but rather re-try the remote call on the next invocation.</p>
</div>
<div class="paragraph">
<p>A simple approach could be to catch the exception and return <code>null</code>, so that the caller can
act accordingly:</p>
</div>
<div class="listingblock">
<div class="title">Sample code</div>
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">    public void caller(int val) {

        Integer result = callRemote(val);  <i class="conum" data-value="1"></i><b>(1)</b>
        if (result == null) {
            System.out.println("Result is " + result);
        else {
           System.out.println("Got an exception");
        }
    }

    @CacheResult(name = "foo")
    private Integer callRemote(int val)  {

        try {
            Integer val = remoteWebServer.getResult(val);  <i class="conum" data-value="2"></i><b>(2)</b>
            return val;
        } catch (Exception e) {
            return null;  <i class="conum" data-value="3"></i><b>(3)</b>
        }
    }</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>Call the method to call the remote</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>Do the remote call and return its result</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>Return in case of exception</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>This approach has an unfortunate side effect: as we said before, Quarkus can also cache
<code>null</code> values. Which means that the next call to <code>callRemote()</code> with the same parameter value
will be answered out of the cache, returning <code>null</code> and no remote call will be done.
This may be desired in some scenarios, but usually one wants to retry the remote call until it returns a result.</p>
</div>
<div class="sect2">
<h3 id="let-exceptions-bubble-up"><a class="anchor" href="#let-exceptions-bubble-up"></a>Let exceptions bubble up</h3>
<div class="paragraph">
<p>To prevent the cache from caching (marker) results from a remote call, we need to let
the exception bubble out of the called method and catch it at the caller side:</p>
</div>
<div class="listingblock">
<div class="title">With Exception bubbling up</div>
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">   public void caller(int val) {
       try {
           Integer result = callRemote(val);  <i class="conum" data-value="1"></i><b>(1)</b>
           System.out.println("Result is " + result);
       } catch (Exception e) {
           System.out.println("Got an exception");
   }

   @CacheResult(name = "foo")
   private Integer callRemote(int val) throws Exception { <i class="conum" data-value="2"></i><b>(2)</b>

      Integer val = remoteWebServer.getResult(val);  <i class="conum" data-value="3"></i><b>(3)</b>
      return val;

   }</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>Call the method to call the remote</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>Exceptions may bubble up</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>This can throw all kinds of remote exceptions</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>When the call to the remote throws an exception, the cache does not store the result,
so that a subsequent call to <code>callRemote()</code> with the same parameter value will not be
answered out of the cache.
It will instead result in another
attempt to call the remote.</p>
</div>
<div class="paragraph">
<p>The previous code example has the side-effect that the cache logs the thrown exception
with a long stack trace to the console. This is done to inform developers that something
exceptional has happened, but if you have a setup like above, you are already catching
the exception and know what you are doing.</p>
</div>
</div>
</div>
</div>
    </div>
  </div>
</div>

  </div>

  <div class="content project-footer">
  <div class="footer-section">
    <div class="logo-wrapper">
      <a href="/"><img src="/assets/images/quarkus_logo_horizontal_rgb_reverse.svg" class="project-logo" title="Quarkus"></a>
    </div>
  </div>
  <div class="grid-wrapper">
    <p class="grid__item width-3-12">Quarkus is open. All dependencies of this project are available under the <a href='https://www.apache.org/licenses/LICENSE-2.0' target='_blank'>Apache Software License 2.0</a> or compatible license.<br /><br />This website was built with <a href='https://jekyllrb.com/' target='_blank'>Jekyll</a>, is hosted on <a href='https://pages.github.com/' target='_blank'>Github Pages</a> and is completely open source. If you want to make it better, <a href='https://github.com/quarkusio/quarkusio.github.io' target='_blank'>fork the website</a> and show us what you’ve got.</p>

    
      <div class="width-1-12 project-links">
        <span>Navigation</span>
        <ul class="footer-links width-1-12">
          
            <li><a href="/">Home</a></li>
          
            <li><a href="/guides">Guides</a></li>
          
            <li><a href="/community/#contributing">Contribute</a></li>
          
            <li><a href="/faq">FAQ</a></li>
          
            <li><a href="/get-started">Get Started</a></li>
          
        </ul>
      </div>
    
      <div class="width-1-12 project-links">
        <span>Contribute</span>
        <ul class="footer-links width-1-12">
          
            <li><a href="https://twitter.com/quarkusio">Follow us</a></li>
          
            <li><a href="https://github.com/quarkusio">GitHub</a></li>
          
            <li><a href="/security">Security&nbsp;policy</a></li>
          
        </ul>
      </div>
    
      <div class="width-1-12 project-links">
        <span>Get Help</span>
        <ul class="footer-links width-1-12">
          
            <li><a href="https://groups.google.com/forum/#!forum/quarkus-dev">Forums</a></li>
          
            <li><a href="https://quarkusio.zulipchat.com">Chatroom</a></li>
          
        </ul>
      </div>
    

    
      <div class="width-3-12 more-links">
        <span>Quarkus is made of community projects</span>
        <ul class="footer-links">
          
            <li><a href="https://vertx.io/" target="_blank">Eclipse Vert.x</a></li>
          
            <li><a href="https://microprofile.io" target="_blank">Eclipse MicroProfile</a></li>
          
            <li><a href="https://hibernate.org" target="_blank">Hibernate</a></li>
          
            <li><a href="https://netty.io" target="_blank">Netty</a></li>
          
            <li><a href="https://resteasy.github.io" target="_blank">RESTEasy</a></li>
          
            <li><a href="https://camel.apache.org" target="_blank">Apache Camel</a></li>
          
            <li><a href="https://code.quarkus.io/" target="_blank">And many more...</a></li>
          
        </ul>
      </div>
    
  </div>
</div>
  <div class="content redhat-footer">
  <div class="grid-wrapper">
    <span class="licence">
      <i class="fab fa-creative-commons"></i><i class="fab fa-creative-commons-by"></i> <a href="https://creativecommons.org/licenses/by/3.0/" target="_blank">CC by 3.0</a> | <a href="https://www.redhat.com/en/about/privacy-policy">Privacy Policy</a>
    </span>
    <span class="redhat">
      Sponsored by
    </span>
    <span class="redhat-logo">
      <a href="https://www.redhat.com/" target="_blank"><img src="/assets/images/redhat_reversed.svg"></a>
    </span>
  </div>
</div>


  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js" integrity="sha384-8gBf6Y4YYq7Jx97PIqmTwLPin4hxIzQw5aDmUg/DDhul9fFpbbLcLh3nTIIDJKhx" crossorigin="anonymous"></script>
  <script type="text/javascript" src="/assets/javascript/mobile-nav.js"></script>
  <script type="text/javascript" src="/assets/javascript/scroll-down.js"></script>
  <script src="/assets/javascript/satellite.js" type="text/javascript"></script>
  <script src="https://quarkus.io/guides/javascript/config.js" type="text/javascript"></script>
  <script src="/assets/javascript/search-filter.js" type="text/javascript"></script>
  <script src="/assets/javascript/back-to-top.js" type="text/javascript"></script>
</body>

</html>
